import { DeleteCertainNumCmd } from './../command'
import { SudokuCeilValue } from '../sudoku'
import {
  InputCertainNumCmd,
  InputCandidateNumCmd,
  DeleteCandidateNumCmd
} from '../command'
import { SudokuGame } from '../game'
import { valueFromAST } from 'graphql'

function makeCandidateFromArray(array: number[]) {
  let result: boolean[] = new Array(9).fill(false)
  for (let arr of array) {
    result[arr - 1] = true
  }
  return result
}

describe('sudoku game', () => {
  let game: SudokuGame
  let getCeil: (colIdx: number, rowIdx: number) => SudokuCeilValue

  beforeEach(() => {
    let arr = new Array(9).fill(null).map(() => new Array(9).fill(-1))
    game = SudokuGame.NewSudokuGame()
    getCeil = (rowIdx: number, colIdx: number) => {
      return game.displayState[rowIdx][colIdx]
    }
  })

  it('should input many candidates command successfully', () => {
    let nums = [1, 3, 5, 6, 3, 5, 1]
    let ansNums = [1, 3, 5, 6]

    nums.forEach(value => {
      game.execute(
        new InputCandidateNumCmd({
          colIdx: 2,
          rowIdx: 2,
          value: value
        })
      )
    })

    let ceil = getCeil(2, 2)
    expect(ceil.candidates).toEqual(makeCandidateFromArray(ansNums))
  })

  it('should input many certain num successfully', () => {
    const colIdx = 1
    const rowIdx = 1

    const nums = [1, 3, 4, 6]
    nums.forEach(value => {
      game.execute(
        new InputCertainNumCmd({
          colIdx,
          rowIdx,
          value
        })
      )
    })
    // should be 6

    let ceil = getCeil(1, 1)
    expect(ceil.mainValue).toBe(6)
  })

  it('should remove certain num', () => {
    const value = 3
    const colIdx = 1
    const rowIdx = 1
    game.execute(
      new InputCertainNumCmd({
        colIdx,
        rowIdx,
        value
      })
    )
    // have value in state
    game.execute(
      new DeleteCertainNumCmd({
        colIdx,
        rowIdx,
        value
      })
    )
    let ceil = getCeil(rowIdx, colIdx)

    expect(ceil.mainValue).toBe(-1)
  })

  it('can undo from remove multi candidates num without error', () => {
    const numToInput = [1, 3, 5, 6]
    const numToDelete = [6, 3, 5]
    const undoNumState = [[1], [1, 5], [1, 3, 5], [1, 3, 5, 6]]
    const colIdx = 1
    const rowIdx = 1

    numToInput.forEach(value => {
      game.execute(
        new InputCandidateNumCmd({
          colIdx,
          rowIdx,
          value
        })
      )
    })

    // delete num
    numToDelete.forEach(value => {
      game.execute(
        new DeleteCandidateNumCmd({
          colIdx,
          rowIdx,
          value
        })
      )
    })

    // undo
    undoNumState.forEach(value => {
      let ceil = getCeil(rowIdx, colIdx)
      expect(ceil.candidates).toEqual(makeCandidateFromArray(value))
      game.Undo()
    })
  })

  it('can recover after input certain num', () => {
    const rowIdx = 2
    const colIdx = 1
    const value = 3
    const numToInput = [1, 3, 5, 6]
    const numToCheck = Array.from(numToInput).reverse()

    numToInput.forEach(value => {
      game.execute(
        new InputCertainNumCmd({
          colIdx,
          rowIdx,
          value
        })
      )
    })

    // check recover
    numToCheck.forEach(value => {
      let ceil = getCeil(rowIdx, colIdx)
      expect(ceil.mainValue).toBe(value)
      // recover
      game.Undo()
    })
  })

  it('can recover after delete certain num', () => {
    const colIdx = 2
    const rowIdx = 3
    const numToInput = 3
    const deleteCnt = 4

    game.execute(
      new InputCertainNumCmd({
        colIdx,
        rowIdx,
        value: numToInput
      })
    )

    new Array(deleteCnt).forEach(() => {
      game.execute(
        new DeleteCertainNumCmd({
          colIdx,
          rowIdx,
          value: 1
        })
      )
    })

    // recover, recover twice
    game.Undo()

    let ceil = getCeil(rowIdx, colIdx)
    expect(ceil.mainValue).toBe(-1)
  })
})
